package com.examples.gateway.loadbalance;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName: ExamplesLoadBalancer
 * @Description: TODO
 * @Author: DAIYALU
 * @Date: 2024/4/9
 * @Version: 1.0
 */
@Slf4j
public class ExamplesLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    final String serviceId;

    final AtomicInteger position;

    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public static final String INSTANCE_TAGS = "tags";

    public ExamplesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId) {
        this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
    }

    public ExamplesLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId, int seedPosition) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.position = new AtomicInteger(seedPosition);
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        // 这里额外传入request对象用于解析请求头
        return supplier.get(request).next()
                .map(serviceInstances -> this.processInstanceResponse(request, supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(Request request, ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances) {
        // request对象带入getInstanceResponse方法
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(request, serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }
        // 获取请求头tags属性值
        String tags = StringUtils.EMPTY;
        // 路由实例
        ServiceInstance instance = null;
        if (null != request.getContext() && request.getContext() instanceof RequestDataContext) {
            List<String> tagsList = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders().get(INSTANCE_TAGS);
            // 有且只取其中一个
            tags = Optional.ofNullable(tagsList).map(m -> m.get(0)).orElse(StringUtils.EMPTY);
        }
        // Metadata.tags 优先匹配
        for (ServiceInstance serviceInstance : instances) {
            // tags一致
            if (StringUtils.equals(serviceInstance.getMetadata().get(INSTANCE_TAGS), tags)) {
                instance = serviceInstance;
                break;
            }
        }
        // instance为空说明未配置tags,或请求头未带tags，走默认路由规则
        if (null == instance) {
            int pos = Math.abs(this.position.incrementAndGet());
            instance = instances.get(pos % instances.size());
        }
        return new DefaultResponse(instance);
    }
}
